登入功能看起來簡單,但其實背後涉及不少技術細節,尤其是在設計和實作的過程中,許多新手開發者會面臨很多問題。比如,「要怎麼確保資料安全?」或者「如何區分不同的使用者權限?」這些都可能感到頭痛。今天這篇文章,我們就來探討一下如何透過 JWT 來建立一個完整的身份驗證流程,讓你輕鬆應對這些挑戰。
當第一次接觸身份驗證時,可能會覺得困難重重。像我當初一樣,曾經一度想著:「該不會要直接把帳號密碼存到資料庫吧?」但這種做法既危險又不實際,資料有很大可能會被駭客竊取。隨著專案的進展,開始接觸 JWT 時,又會擔心 Token 是否安全、如何管理不同的使用者權限,以及該怎麼確保登入狀態。
不過,當掌握了 JWT 的概念並實作完成身份驗證功能後,真的會感到成就滿滿!從這個過程中,你會學到許多關於後端安全性和用戶管理的知識,還會幫助你在開發技能上更上一層樓。
內容接下來很多,走囉!
現在來介紹登入功能的核心工具 —— JWT(JSON Web Token)!JWT 讓你在身份驗證的流程中保持簡單而安全,伺服器可以通過這個 Token 確認用戶身份,從而省去每次查詢資料庫的麻煩。
JWT 是一個包含三部分的 Token,分別是 Header、Payload 和 Signature。
HS256
)。userId
或 roles
),伺服器可以通過這部分來確認用戶身份。JWT 的好處在於,它可以讓伺服器認識到用戶的身份而不必每次都查詢資料庫,節省效能並確保安全性。
想要看看一個實際的 JWT 長什麼樣?你可以前往 JWT 官方網站,那裡有個工具讓你可以解密和測試 JWT!你只需要將一個 Token 貼到 encoded 區域,就能解析出 Header、Payload 和 Signature。
你會看到 Token 的三個部分清楚地顯示出來,了解每一個部分的內容和作用,這對我來說是非常好用的工具,具體步驟如下:
Encoded
區域。Decoded
區域的 Header、Payload 和 Signature 逐一解析出來,了解 Token 的內容結構。接下來我們將一步步教你如何在 Node.js + Express 中實作 JWT,生成和驗證 Token。
jsonwebtoken
和 dotenv
我們首先需要安裝 jsonwebtoken
來生成 JWT,同時使用 dotenv
管理環境變數,這樣可以避免將密鑰直接寫死在程式碼裡。
npm install jsonwebtoken dotenv
為了確保 JWT 的密鑰不會被洩漏,我們將它存放在 .env
檔案中,這樣可以避免密鑰直接暴露在程式碼中。
在專案的根目錄創建一個 .env
檔案,並設定密鑰:
JWT_SECRET=yourSecretKeyHere
這樣你的密鑰就會存放在環境變數裡,而不是直接寫在程式碼中,這樣可以提高安全性。
接著,我們需要在程式入口檔案(如 app.js
或 server.js
)中引入 dotenv
,來讀取環境變數中的密鑰。
require('dotenv').config();
這樣,我們就可以在程式碼中使用 process.env.JWT_SECRET
來讀取密鑰。
當用戶成功登入時,後端會生成一個 JWT 並發送給前端。這裡展示的是一個基本的登入流程,當用戶驗證成功後,我們生成 JWT,並將它回傳給前端。
controllers/authController.js:
const jwt = require('jsonwebtoken');
// 登入成功後生成 JWT
exports.login = (req, res) => {
const { username, password } = req.body;
// 假設用戶驗證成功
const userId = 1; // 這裡的 userId 是從資料庫中取得的用戶ID
const token = jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '1h' }); // 生成 JWT
res.json({ token });
};
jwt.sign()
:這個方法會生成一個 JWT。{ userId }
:這是我們想要存進 Token 的數據。在這裡,我們把用戶的 ID 存進去,後續我們就可以通過這個 Token 確認用戶身份。process.env.JWT_SECRET
:這是從環境變數中讀取的密鑰,確保密鑰不會暴露在程式碼中。{ expiresIn: '1h' }
:這個選項設定 Token 的有效期。在這裡,我們設定 Token 一小時後過期,這樣可以提高系統的安全性。authController
路由你需要在 app.js
中引入並使用該路由。可以參考以下步驟:
app.js
var authController = require('./controllers/authController'); // 確保引入 authController
app.post('/login', authController.login); // 為 /login 路徑設置 POST 路由
這樣可以讓 Express 知道 /login
的請求應該由 authController.login
處理。
為了確保 JWT 的安全性,我們需要使用足夠強度的密鑰。你可以使用 randomkeygen.com 來生成一個隨機密鑰,具體步驟如下:
.env
檔案中:JWT_SECRET=dc18b603874e5e42d60dd9a9d712d8edfe017bce4792d3e676db9083ed1a6769
這樣,你的密鑰就足夠隨機且安全,能夠有效防範潛在的安全風險。
在開發後端 API 的時候,怎麼確保只有登入過的用戶才能看到重要資料?這就是 JWT 的用武之地!我們可以用 JWT 來做身份驗證,確保那些敏感的 API 路由不會被隨便亂存取。要達成這個目的,我們會用到一個叫 中介層(Middleware) 的東西。
中介層就像是 API 路由的守門員一樣,負責在請求到達路由之前,先檢查這個請求合不合法。它可以在請求進入具體路由前,先幫你做一些檢查動作。在我們的例子中,這個中介層會檢查用戶的請求中有沒有帶著有效的 JWT。如果沒有,那就直接拒絕!如果有,且通過驗證,就讓請求繼續下去。
verifyToken
中介層?這個中介層超重要,因為它幫我們把關 API 的安全性。很多時候,我們的 API 只允許登入的用戶存取,例如:查詢個人資料、刪除資料、更新設定等等。如果沒有 JWT 做驗證,那任何人都能隨便發送請求來修改資料,這樣就太危險了!
透過 verifyToken
中介層,我們可以確保只有那些帶著有效 JWT 的用戶才能存取這些 API,這樣不但提高了安全性,也讓 API 更具保護性。
Authorization
Header,告訴後端:「我有 Token,快讓我進去吧!」。verifyToken
中介層會讀取請求的 Header,檢查有沒有帶上 JWT。如果沒有,那就直接回應「403 禁止存取」。如果有帶 Token,就會用伺服器上的密鑰來檢查這個 Token 是否有效。verifyToken
中介層?來看看我們如何在 middlewares/authMiddleware.js
中寫一個 verifyToken
函數:
const jwt = require('jsonwebtoken');
// 驗證 JWT Token 的中介層
exports.verifyToken = (req, res, next) => {
const token = req.headers['authorization']; // 從請求的 Headers 中取出 JWT
// 如果請求中沒有 Token,回應錯誤訊息
if (!token) {
return res.status(403).json({ message: 'Token is required' }); // 403:禁止存取
}
try {
// 使用密鑰驗證 Token,成功後把解碼的資料加到請求物件中
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.userId = decoded.userId; // 把 userId 保存到請求物件中
next(); // 通過驗證,繼續執行下一個中介層或路由處理程式
} catch (err) {
return res.status(401).json({ message: 'Invalid Token' }); // 401:身份驗證失敗
}
};
userId
加到請求物件上,這樣我們就知道這個請求是誰發的。verifyToken
?每當有 API 路由需要保護的時候,我們都會用到這個中介層。比如說,你有一個 API 是用來讓用戶查詢個人資料的,那我們就要確保這個路由只能被已登入的用戶存取。我們可以在這個路由中加上 verifyToken
,這樣每次有人試圖存取這個 API,後端都會先檢查他的 Token。
verifyToken
保護路由假設我們有一個 API 路由是 /api/products
,這是只有已登入的用戶才能存取的。那麼我們可以這樣寫:
// routes/productRoutes.js
const express = require('express');
const router = express.Router();
const { verifyToken } = require('../middlewares/authMiddleware');
// 保護 /products 路由,只允許已登入用戶存取
router.get('/products', verifyToken, (req, res) => {
res.json({ message: '這是產品資料,只有登入用戶才能看到!' });
});
module.exports = router;
這樣,當用戶試圖存取 /api/products
時,後端會先檢查他的 JWT。如果 Token 有效,就可以順利返回產品資料;如果無效,就會被擋在門外。
前端發送請求:用戶登入後,前端會把 JWT 放在請求的 Authorization
Header 中,每次發送 API 請求都帶著這個 Token。
const token = localStorage.getItem('token');
const headers = { 'Authorization': `Bearer ${token}` };
fetch('http://localhost:3000/api/products', { headers })
.then(response => response.json())
.then(data => console.log(data));
後端驗證 Token:後端的 verifyToken
中介層會讀取請求的 Authorization
Header,檢查這個 Token 是否存在並且有效。如果驗證通過,就讓請求繼續執行;如果失敗,回應「401 Unauthorized」。
API 請求流程:通過 JWT 驗證後,後端就會執行對應的邏輯,比如說返回資料給用戶。如果沒有通過驗證,後端會回應錯誤訊息,拒絕存取。
既然我們的 JWT 實作好了,那接下來就要測試是否能正常運作。這裡我們會用 Postman 來模擬前端發送請求,並驗證我們的後端流程。
http://localhost:3000/login
。{
"username": "testuser",
"password": "password"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
這個回應中的 token
就是 JWT,之後的每次請求,我們都會帶著這個 Token。
JWT 是一個簡單但功能強大的工具,能夠幫助我們實現安全高效的身份驗證。在實作 JWT 時,從生成 Token 到驗證 Token,整個流程非常直觀,但仍然需要注意安全細節。將密鑰儲存在環境變數中,並使用強隨機密鑰來加強安全性,是我們應該遵循的基本安全措施。
在本文中,我們介紹了如何在後端 Node.js(Express) 中實作 JWT,包括生成和驗證 Token 的具體步驟,還提供了如何保護後端資源的範例。透過 JWT,你可以有效地管理使用者的登入狀態和權限,讓整個系統的安全性更上層樓。
接下來,會在下一篇文章中介紹如何在 Angular 前端 使用這個 JWT,讓前後端無縫整合,實現完整的身份驗證流程。